﻿using System.Collections.Generic;
using UnityEngine;

namespace TinyWnd
{
    public class WndShowList
    {
        WndType _type;
        List<WndId> _showIds = new List<WndId>(); // 新显示的窗口 id 存放在末尾

        public WndType type { get { return _type; } }
        public int count { get { return _showIds.Count; } }

        public void Init(WndType type)
        {
            _type = type;
        }

        public int IndexOfId(WndId id)
        {
            for (var i = _showIds.Count - 1; i >= 0; i--)
            {
                if (_showIds[i] == id)
                    return i;
            }
            return -1;
        }

        public void RemoveAt(int index)
        {
            if (index >= 0 && index < _showIds.Count)
                _showIds.RemoveAt(index);
        }

        public WndId PeekId()
        {
            return _showIds.Count == 0 ? WndId.none : _showIds[_showIds.Count - 1];
        }

        public void PushId(WndId id)
        {
            _showIds.Add(id);
        }

        public void Remove(WndId id)
        {
            var index = IndexOfId(id);
            RemoveAt(index);
        }

        public void HideWnd()
        {
            WndId id = PeekId();
            if (id == WndId.none)
                return;
            Wnd wnd = WndMgr.inst.GetWnd(id);
            wnd.HideDirectly();
        }

        public void ShowWnd()
        {
            WndId id = PeekId();
            if (id == WndId.none)
                return;
            Wnd wnd = WndMgr.inst.GetWnd(id);
            wnd.ShowDirectly();
        }

        public static WndShowList Create(WndType type)
        {
            var info = new WndShowList();
            info.Init(type);
            return info;
        }
    }

    public class WndMgr : MonoBehaviour
    {
        static WndMgr s_inst;
        public static WndMgr inst { get { return s_inst; } }

        const int normalStartDepth = 10000;
        const int toolbarStartDepth = 20000;
        const int popupStartDepth = 30000;

        [SerializeField]
        Transform root;
        [SerializeField]
        UIAtlas mastAtlas;

        Transform _rootNormal;
        Transform _rootToolbar;
        Transform _rootPopup;

        Dictionary<WndId, Wnd> _allWnds;
        Dictionary<WndType, WndShowList> _showInfoDict;

        void Awake()
        {
            s_inst = this;
            InitVariables();
            InitContext();
        }

        void InitVariables()
        {
            _allWnds = new Dictionary<WndId, Wnd>();
            
            _showInfoDict = new Dictionary<WndType, WndShowList>();
            _showInfoDict.Add(WndType.normal, WndShowList.Create(WndType.normal));
            _showInfoDict.Add(WndType.toolbar, WndShowList.Create(WndType.toolbar));
            _showInfoDict.Add(WndType.popup, WndShowList.Create(WndType.popup));
        }

        void InitContext()
        {
            var layer = root.gameObject.layer;
            if (_rootNormal == null)
            {
                _rootNormal = new GameObject("RootNormal").transform;
                Utility.AddChild(root, _rootNormal);
            }

            if (_rootToolbar == null)
            {
                _rootToolbar = new GameObject("RootToolbar").transform;
                Utility.AddChild(root, _rootToolbar);
            }

            if (_rootPopup == null)
            {
                _rootPopup = new GameObject("RootPopup").transform;
                Utility.AddChild(root, _rootPopup);
            }
        }

        WndShowList GetShowList(WndType type)
        {
            return _showInfoDict[type];
        }

        void AddWnd(Wnd wnd)
        {
            _allWnds[wnd.id] = wnd;
        }

        Wnd LoadWndResource(WndId id)
        {
            string path = WndResource.GetPrefbPath(id);
            if (string.IsNullOrEmpty(path))
            {
                Log.Error<WndMgr>("no prefab file, wnd:{0}", id);
                return null;
            }

            GameObject prefab = Resources.Load<GameObject>(path);
            if (prefab == null)
            {
                Log.Error<WndMgr>("fail to load prefab, wnd:{0}", id);
                return null;
            }

            GameObject go = Instantiate<GameObject>(prefab);
            go.SetActive(true);
            Wnd wnd = go.GetComponent<Wnd>();
            if (wnd == null || wnd.id != id)
            {
                Log.Error<WndMgr>("wrong prefab script, wnd:{0}", id);
                return null;
            }
            return wnd;
        }

        Wnd LoadWnd(WndId id)
        {
            Wnd wnd = GetWnd(id);
            if (wnd == null)
            {
                wnd = LoadWndResource(id);
                if (wnd == null)
                    return null;
                Transform wndRoot = GetWndRoot(wnd.type);
                Utility.AddChild(wndRoot, wnd.transform);
                wnd.gameObject.SetActive(false); // 此时 Awake 已经完成，但设置为 inactive，是否可见在 ShowWindow 中完成。
            }
            return wnd;
        }

        public Wnd GetWnd(WndId id)
        {
            Wnd w;
            _allWnds.TryGetValue(id, out w);
            return w;
        }

        public T GetWnd<T>(WndId id) where T : Wnd
        {
            Wnd w = GetWnd(id);
            if (w != null)
                return w as T;
            else
                return null;
        }

        public Transform GetWndRoot(WndType type)
        {
            if (type == WndType.normal)
                return _rootNormal;
            else if (type == WndType.toolbar)
                return _rootToolbar;
            else
                return _rootNormal;
        }

        public int GetStartDepth(WndType type)
        {
            if (type == WndType.toolbar)
                return toolbarStartDepth;
            else if (type == WndType.popup)
                return popupStartDepth;
            else
                return normalStartDepth;
        }

		// 打开新的窗口设置 depth 。暂时不支持在已打开的窗口中动态加载新的 pane 。
        bool SetWndDepth(Wnd wnd)
        {
            int startDepth = GetStartDepth(wnd.type);
            List<UIPanel> lst = wnd.GetSortedPanels(true);
            if (lst.Count == 0)
            {
                Log.Error<WndMgr>("set depth buy no panel, type:{0}", wnd.type);
                return false;
            }

            for (int i = 0; i < lst.Count; i++)
                lst[i].depth = startDepth + i;
            return true;
        }

        bool SetWndBg(Wnd wnd)
        {
            if (wnd.colliderMode == WndColliderMode.none)
                return true;

            GameObject go = null;
            if (wnd.colliderMode == WndColliderMode.transparent)
                go = Utility.AddColliderBg(wnd.transform, "Window", mastAtlas, true);
            else if (wnd.colliderMode == WndColliderMode.opaque)
                go = Utility.AddColliderBg(wnd.transform, "Window", mastAtlas, false);
            if (go == null)
                return false;

            wnd.OnAddColliderBg(go);
            return true;
        }

        bool PrepareShowWindow(Wnd wnd)
        {
            WndShowList showLst = GetShowList(wnd.type);
            // 检查窗口是否之前被显示过
            int index = showLst.IndexOfId(wnd.id);
            if (index >= 0)
            {
                wnd.Hide(); // 已经显示的窗口先隐藏再重新显示，但动画需要多帧执行才能完成，若窗口带有动画可能需要特别注意。
                showLst.RemoveAt(index);
            }

            // 隐藏当前显示的窗口
            showLst.HideWnd();
            return true;
        }

        void AfterShowWindow(Wnd wnd)
        {
            AddWnd(wnd);
            WndShowList showLst = GetShowList(wnd.type);
            showLst.PushId(wnd.id);
        }

        public void ShowWindow(WndId id, WndContentData content = null)
        {
            Wnd wnd = LoadWnd(id);
            if (wnd == null)
                return;
            if (!PrepareShowWindow(wnd))
                return;
            if (!SetWndDepth(wnd))
                return;
            if (!SetWndBg(wnd))
                return;

            wnd.Show(content);

            AfterShowWindow(wnd);
        }

        public void PopWindow(WndType type = WndType.normal)
        {
            WndShowList showLst = GetShowList(type);
            WndId id = showLst.PeekId();
            if (id == WndId.none)
                return;
            Wnd wnd = GetWnd(id);
            if (!wnd.InvokeReturnCallback())
                return;
            if (wnd.unifiedClose)
                CloseWindow(id, true);
        }

        public void CloseWindow(WndId id, bool showPrev = false)
        {
            Wnd wnd = GetWnd(id);
            if (wnd == null)
            {
                Log.Error<WndMgr>("close wnd but no data, id:{0}", id);
                return;
            }

            wnd.Hide();

            WndShowList showLst = GetShowList(wnd.type);
            showLst.Remove(id);
            if (showPrev)
                showLst.ShowWnd();
        }

        public void HideWnds()
        {
            foreach (var one in _showInfoDict.Values)
                one.HideWnd();
        }

        public void RestoreWnds()
        {
            foreach (var one in _showInfoDict.Values)
                one.ShowWnd();
        }
    }
}
